/**
 * GambatteDroid
 * Copyright 2011 Stephen Damm (Halsafar)
 * All rights reserved.
 * shinhalsafar@gmail.com
 */


#include "Application.h"

#include "libzip/zip.h"

static Gambatte::GB gbEmu;

Application::Application():
		Input(BUTTON_INDEX_COUNT, 1)
{
     _initialized = false;
     _romLoaded = false;
     _fceuInitialized = false;
     _audioInitialized = false;
     _ssize = 0;
     _sampleRate = 22050;
     _resampler = NULL;

     _emuSamples = 0;
     _tmpSamples = 0;

     // paths
     _apkPath = (char*)malloc(MAX_PATH * sizeof(char));
     _stateDir = (char*)malloc(MAX_PATH * sizeof(char));
     _sramDir = (char*)malloc(MAX_PATH * sizeof(char));
     _currentRom = (char*)malloc(MAX_PATH * sizeof(char));

     _viewPortW = EMU_RENDER_TEXTURE_WIDTH;
     _viewPortH = EMU_RENDER_TEXTURE_HEIGHT;
}


Application::~Application()
{
	LOGD("Application::Destructor()");

     destroy();
}


void Application::destroy()
{
     LOGD("Start Application.destroy()");

     // TODO: take auto save snapshot

     // save sram
     saveSRam(_sramDir);

     // TODO - shutdown

     _romLoaded = false;

     LOGD("Finished Application.destroy()");
}


int Application::init(JNIEnv *env, const char * apkAbsolutePath, const char * externalStorageDir,
		const char * stateDir, const char * sramDir)
{
     LOGD("APK_PATH: %s", apkAbsolutePath);
     LOGD("EXTERNAL_STORAGE_DIR: %s", externalStorageDir);
     LOGD("STATE DIR: %s", stateDir);
     LOGD("SRAM DIR: %s", sramDir);

     // TODO: setup genesis directories

     // copy dirs for storage
     if (apkAbsolutePath == NULL || strlen(apkAbsolutePath) >= MAX_PATH)
     {
    	 return NATIVE_ERROR;
     }

     strcpy(_apkPath, apkAbsolutePath);

     if (stateDir == NULL || strlen(stateDir) >= MAX_PATH)
     {
    	 return NATIVE_ERROR;
     }

     strcpy(_stateDir, stateDir);

     if (sramDir == NULL || strlen(sramDir) >= MAX_PATH)
     {
    	 return NATIVE_ERROR;
     }

     strcpy(_sramDir, sramDir);


     if (!_fceuInitialized)
     {
    	 gbEmu.setSaveDir(_sramDir);

    	 _fceuInitialized = true;
     }

     _initialized = true;

     return NATIVE_OK;
}


int Application::initAudioBuffers(const int sizeInSamples)
{
     LOGD("initAudioBuffers(%d)", sizeInSamples);

     _audioBufferSize = sizeInSamples;
     _audioInitialized = true;

     return NATIVE_OK;
}


int Application::initGraphics()
{
     Graphics.Init();

     Graphics.InitEmuShader(NULL, NULL);
     Graphics.Clear();

     LOGI("Loading APK %s", _apkPath);
     zip* APKArchive = zip_open(_apkPath, 0, NULL);
     if (APKArchive == NULL)
     {
          LOGE("Error loading APK");
          return NATIVE_ERROR;
     }

     int dPadTex = 0;
	  if (Graphics.loadPNGTexture(APKArchive, "assets/Textures/DirectionalPad.png", &dPadTex) != GRAPHICS_OK)
	  {
		   return NATIVE_ERROR;
	  }

	  int startTex = 0;
	  if (Graphics.loadPNGTexture(APKArchive, "assets/Textures/StartButton.png", &startTex) != GRAPHICS_OK)
	  {
		   return NATIVE_ERROR;
	  }

	  int selectTex = 0;
	  if (Graphics.loadPNGTexture(APKArchive, "assets/Textures/SelectButton.png", &selectTex) != GRAPHICS_OK)
	  {
		   return NATIVE_ERROR;
	  }

	  int bTex = 0;
	  if (Graphics.loadPNGTexture(APKArchive, "assets/Textures/ButtonB.png", &bTex) != GRAPHICS_OK)
	  {
		   return NATIVE_ERROR;
	  }

	  int aTex = 0;
	  if (Graphics.loadPNGTexture(APKArchive, "assets/Textures/ButtonA.png", &aTex) != GRAPHICS_OK)
	  {
		   return NATIVE_ERROR;
	  }


	  int rewindTex = 0;
	  if (Graphics.loadPNGTexture(APKArchive, "assets/Textures/Rewind.png", &rewindTex) != GRAPHICS_OK)
	  {
		   return NATIVE_ERROR;
	  }

	  int fastForwardTex = 0;
	  if (Graphics.loadPNGTexture(APKArchive, "assets/Textures/FastForward.png", &fastForwardTex) != GRAPHICS_OK)
	  {
		   return NATIVE_ERROR;
	  }

     zip_close(APKArchive);

     Input.setAnalogTexture(0, dPadTex);
     Input.setButtonTexture(BUTTON_INDEX_B, bTex);
     Input.setButtonTexture(BUTTON_INDEX_A, aTex);
     Input.setButtonTexture(BUTTON_INDEX_START, startTex);
     Input.setButtonTexture(BUTTON_INDEX_SELECT, selectTex);
     Input.setButtonTexture(BUTTON_INDEX_REWIND, rewindTex);
     Input.setButtonTexture(BUTTON_INDEX_FORWARD, fastForwardTex);

     return NATIVE_OK;
}


void Application::setAudioSampleRate(int rate)
{
	bool hardReset = false;
	if (_sampleRate != rate)
	{
		hardReset = true;
	}

	_sampleRate = rate;

	if (_romLoaded && hardReset)
	{
		// TODO: wont catch auto saves
		loadROM(_currentRom);
	}
}


void Application::setAudioEnabled(const bool b)
{
     if (b)
     {
          _audioInitialized = true;
     }
     else
     {
          _audioInitialized = false;
     }
}


void Application::saveState(const int i)
{
	char path[MAX_PATH];
	makePath(_stateDir, path);
	sprintf(path, "%s%d.sav", path, i);

	LOGD("SaveState: %s", path);

	gbEmu.saveState(_screenBuff, 160, path);
}


void Application::loadState(const int i)
{
	char path[MAX_PATH];
	makePath(_stateDir, path);
	sprintf(path, "%s%d.sav", path, i);

	LOGD("LoadState: %s", path);

	gbEmu.loadState(path);
}


void Application::selectState(const int i)
{

}


void Application::resetROM()
{
	gbEmu.reset();
}


int Application::loadROM(const char* filename)
{
	LOGD("NDK:LoadingRom: %s", filename);

	if (_romLoaded)
	{
		// gambetta does all the work for us when we load a new rom
		_romLoaded = false;
	}

   	// init input
   	gbEmu.setInputGetter(&gbcInput);

     /* load ROM file */
	if (gbEmu.load(filename, false))
	{
		LOGE("Failed to load rom!");
		return NATIVE_ERROR;
	}

	// graphics stuff
	Graphics.ReshapeEmuTexture(EMU_RENDER_TEXTURE_WIDTH, EMU_RENDER_TEXTURE_HEIGHT);

   	// store current rom
   	strcpy(_currentRom, filename);

   	// audio
   	if (_resampler != NULL)
   	{
   		delete _resampler;
   	}

    _resampler = ResamplerInfo::get(2).create(2097152, _sampleRate, 35112 + 2064);
    for (int i = 0; i < ResamplerInfo::num(); i++)
    {
    	LOGD("Resampler[%d] = %s", i, ResamplerInfo::get(i).desc);
    }
    _tmpSamples = 0;
    memset(_audioBuffer, 0, sizeof(short) * SB_SIZE);
    memset(_gbAudioBuffer, 0, sizeof(uint16_t) * GB_SAMPLES);

    LOGD("Successfully Loaded Rom!");
    _romLoaded = true;

    return NATIVE_OK;
}


void Application::loadSRam(const char* romName)
{
	char path[MAX_PATH];
	makePath(_sramDir, path);
	sprintf(path, "%s.srm", path);

	LOGD("Loading SRAM: %s", path);

	// TODO - load sram, not needed in gambatte
}


void Application::saveSRam(const char* romName)
{
	char path[MAX_PATH];
	makePath(_sramDir, path);
	sprintf(path, "%s.srm", path);

	LOGD("Saving SRAM: %s", path);

	// TODO - save sram, not needed in gambatte
}


int Application::makePath(const char* dirName, char* out)
{
	LOGD("makeDir(%s)", dirName);

	char* dotIndex = strrchr(_currentRom, '.');
	char* slashIndex = strrchr(_currentRom, '/') + 1;

	if (dotIndex == NULL || slashIndex == NULL)
	{
		return NATIVE_ERROR;
	}

	*dotIndex = '\0';

	sprintf(out, "%s/%s", dirName, slashIndex);

	*dotIndex = '.';

	return NATIVE_OK;
}


// consumer, java driven
size_t Application::readAudio(jshort* in, const size_t samples)
{
     if (_ssize > 0 && _ssize <= SB_SIZE && _audioBuffer != NULL)
     {
          memcpy(in, _audioBuffer, _ssize * sizeof(short));
     }

     return _ssize;
}


void Application::processInput()
{
	gbcInput.bits = 0;

    if (Input.isButtonDown(BUTTON_INDEX_DOWN) ||
        Input.getAnalogY(0) > Input.getYSensitivity())
    {
    	gbcInput.bits |= Gambatte::InputGetter::DOWN;
    }

    if (Input.isButtonDown(BUTTON_INDEX_RIGHT) ||
        Input.getAnalogX(0) > Input.getXSensitivity())
    {
    	gbcInput.bits |= Gambatte::InputGetter::RIGHT;
    }

    if (Input.isButtonDown(BUTTON_INDEX_LEFT) ||
        Input.getAnalogX(0) < -Input.getXSensitivity())
    {
    	gbcInput.bits |= Gambatte::InputGetter::LEFT;
    }

    if (Input.isButtonDown(BUTTON_INDEX_UP) ||
        Input.getAnalogY(0) < -Input.getYSensitivity())
    {
    	gbcInput.bits |= Gambatte::InputGetter::UP;
    }

	if (Input.isButtonDown(BUTTON_INDEX_A))
	{
		gbcInput.bits |= Gambatte::InputGetter::A;
	}

	if (Input.isButtonDown(BUTTON_INDEX_B))
	{
		gbcInput.bits |= Gambatte::InputGetter::B;
	}

	if (Input.isButtonDown(BUTTON_INDEX_START))
	{
		gbcInput.bits |= Gambatte::InputGetter::START;
	}

	if (Input.isButtonDown(BUTTON_INDEX_SELECT))
	{
		gbcInput.bits |= Gambatte::InputGetter::SELECT;
	}

     if (Input.isButtonDown(BUTTON_INDEX_FORWARD))
     {
          //_frameSkipCount=(_frameSkipCount+1)%(18+1);
    	 unsigned samples = 35112;
          for (int i = 0; i < TURBO_FRAMES_TO_EMULATE; i++)
          {
        	  gbEmu.runFor(NULL, _viewPortW, (uint32_t*)_gbAudioBuffer, samples);
          }
     }

     if (Input.wasButtonPressed(BUTTON_INDEX_REWIND))
     {
    	 // TODO: rewind
     }

     Input.update();
}


void Application::step(const JNIEnv *env)
{
     // quick exit if no rom loaded to step
     if (!_romLoaded)
     {
          return;
     }

     processInput();

     // calculate frameskipping
     _frameSkipCount=(_frameSkipCount+1)%(_frameSkip+1);

     // emulate step
     _emuSamples = 35112;
     int frameSample = gbEmu.runFor((_frameSkipCount == 0 ? _screenBuff : NULL),
    		 	 	 	 	 	 	 _viewPortW,
    		 	 	 	 	 	 	 _gbAudioBuffer,
    		 	 	 	 	 	 	 _emuSamples);
     /*if(frameSample == -1)
     {
    	 LOGD("no emulated frame with %d samples", _emuSamples);
     }*/
     //else logMsg("emulated frame at %d with %d samples", frameSample, samples);
     if(_emuSamples < 30000)
     {
    	 //LOGD("only %d", _emuSamples);
    	 _emuSamples = 35112;
     }
     _ssize = _resampler->resample(_audioBuffer, (const short*)_gbAudioBuffer, _emuSamples);
     _ssize *= 2;

     // emulate step
     /*_emuSamples = 35112 - _tmpSamples;
     if (_sampleRate == 0)
     {
    	 _emuSamples = 0;
     }

     int frameSample = gbEmu.runFor((_frameSkipCount == 0 ? _screenBuff : NULL),
    		 	 	 	 	 	 	 _viewPortW,
    		 	 	 	 	 	 	 reinterpret_cast<Gambatte::uint_least32_t*>(static_cast<uint16_t*>(_gbAudioBuffer)) + _tmpSamples,
    		 	 	 	 	 	 	 _emuSamples);
     if(frameSample >= 0)
     {
         const unsigned insamples = frameSample < 0 ? _tmpSamples + _emuSamples : _tmpSamples + frameSample;
         _tmpSamples += _emuSamples;
         _tmpSamples -= insamples;

         _ssize = _resampler->resample(_audioBuffer, (const short*)_gbAudioBuffer, insamples);
         _ssize *= 2;

         memmove(_gbAudioBuffer, _gbAudioBuffer + insamples * 2, _tmpSamples * sizeof(uint16_t) * 2);
         //LOGD("NumSamples: %d", _ssize);
     }
     else
     {
    	 LOGD("no emulated frame with %d samples", _emuSamples);
    	 _ssize = 0;
     }*/

     Graphics.DrawEmu((uint8_t*)_screenBuff, EMU_RENDER_TEXTURE_WIDTH, EMU_RENDER_TEXTURE_HEIGHT);
     //LOGD("DONE STEP");
}


void Application::draw(const JNIEnv* env)
{
    Graphics.Clear();

    Graphics.Draw();

    Input.draw(Graphics);
}
